package org.jdsm.flow.core.users.impl.jpa.dao;

import org.jdsm.flow.core.users.IGroup;
import org.jdsm.flow.core.users.IRole;
import org.jdsm.flow.core.users.IUser;
import org.jdsm.flow.core.users.dao.IUserDao;
import org.jdsm.flow.core.users.impl.jpa.Group;
import org.jdsm.flow.core.users.impl.jpa.Role;
import org.jdsm.flow.core.users.impl.jpa.User;
import org.jdsm.flow.core.utils.impl.jpa.PersistenceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import javax.persistence.*;
import java.util.*;

/**
 * User: mikom
 * Date: 3/14/13
 * Time: 5:14 PM
 */
public class ComplexDAO implements IUserDao<Long> {

    private static final Logger log = LoggerFactory.getLogger(ComplexDAO.class);

    @PersistenceUnit
    protected EntityManagerFactory entityManagerFactory;
    private EntityManager em;

    @Resource
    private UserDAO userDAO;
    @Resource
    private RoleDAO roleDAO;
    @Resource
    private GroupDAO groupDAO;

    public ComplexDAO() {
    }

    protected EntityManager getEntityManager() {
        if (em == null) {
            em = entityManagerFactory.createEntityManager();
        }
        return em;
    }

    @Override
    public IUser findUserById(Long identifier) {
        return userDAO.find(identifier);
    }

    @Override
    public IUser loadUser(IUser user) {
        return this.findUserById((Long) user.getId());
    }

    @Override
    public Collection<IRole> findUserRolesByUserId(Long identifier) {
        if (identifier == null) {
            log.warn("user's identifier can't be null");
            return Collections.emptyList();
        }
        try {
            TypedQuery<Role> tq = getEntityManager().createNamedQuery("user.findRoles", Role.class)
                    .setParameter("userId", identifier);
            List<Role> rs = tq.getResultList();
            if (rs.size() > 0 && rs.get(0) == null) {
                return new HashSet<IRole>();
            }
            if (log.isDebugEnabled()) {
                for (IRole role : rs) {
                    log.debug("role in result: {}", role);
                }
            }
            return new HashSet<IRole>(rs);
        } catch (NoResultException e) {
            log.warn("could not found roles by user id:{}", identifier);
            log.debug("trace: ", e);
            return Collections.emptyList();
        }
    }

    @Override
    public Collection<IGroup> findUserGroupsByUserId(Long identifier) {
        if (identifier == null) {
            log.warn("user's identifier can't be null");
            return Collections.emptyList();
        }
        try {
            TypedQuery<Group> tq = getEntityManager().createNamedQuery("user.findGroups", Group.class)
                    .setParameter("userId", identifier);
            List<Group> gs = tq.getResultList();
            if (log.isDebugEnabled()) {
                for (IGroup group : gs) {
                    log.debug("group in result: {}", group);
                }
            }
            if(gs.size() > 0 && gs.get(0) == null){
                return Collections.emptySet();
            }
            return new HashSet<IGroup>(gs);
        } catch (NoResultException e) {
            log.warn("could not found  groups by user id:{}", identifier);
            log.debug("trace: ", e);
            return Collections.emptyList();
        }
    }

    @Override
    public IRole findRoleById(Long id) {
        return roleDAO.find(id);
    }

    @Override
    public IRole loadRole(IRole role) {
        if (role == null) {
            log.warn("instance of IRole should be not null");
            return null;
        }
        return roleDAO.find((Long) role.getId());
    }

    @Override
    public Collection<IUser> findUsersForRole(IRole role) {
        if (role == null) {
            log.warn("role can't be null");
            return Collections.emptySet();
        }
        Role r = roleDAO.find((Long) role.getId());
        if (r == null) {
            log.warn("can't found role : {}", role);
            return Collections.emptySet();
        }
        try {
            TypedQuery<User> tq = getEntityManager().createNamedQuery("role.findUsers", User.class)
                    .setParameter("role", r);
            List<User> us = tq.getResultList();
            if(us.size() > 0 && us.get(0) == null){
                return Collections.emptySet();
            }
            if (log.isDebugEnabled()) {
                for (IUser user : us) {
                    log.debug("user in result: {}", user);
                }
            }
            return new HashSet<IUser>(us);
        } catch (NoResultException e) {
            log.warn("could not found users by role:{}", role);
            log.debug("trace: ", e);
            return Collections.emptySet();
        }
    }

    @Override
    public IGroup findGroupById(Long id) {
        return groupDAO.find(id);
    }

    @Override
    public IGroup loadGroup(IGroup group) {
        if (group == null) {
            log.warn("instance of IGroup should be not null");
            return null;
        }
        return groupDAO.find((Long) group.getId());
    }

    @Override
    public Collection<IUser> findUsersForGroup(IGroup group) {
        if (group == null) {
            log.warn("role can't be null");
            return Collections.emptyList();
        }
        Group g = (Group) loadGroup(group);
        if (g == null) {
            log.warn("can't found role : {}", group);
            return Collections.emptyList();
        }
        try {
            TypedQuery<User> tq = getEntityManager().createNamedQuery("group.findUsers", User.class)
                    .setParameter("group", g);
            List<User> us = tq.getResultList();
            if(us.size() > 0 && us.get(0) == null){
                return Collections.emptySet();
            }
            if (log.isDebugEnabled()) {
                for (IUser user : us) {
                    log.debug("user in result: {}", user);
                }
            }
            return new HashSet<IUser>(us);
        } catch (NoResultException e) {
            log.warn("could not found users by group:{}", groupCount());
            log.debug("trace: ", e);
            return Collections.emptyList();
        }
    }

    @Override
    public Collection<IUser> loadUsersByCriteria(int maxResult, int startResult, boolean isSort, String field) {
        return userDAO.load(maxResult, startResult, isSort, field);
    }

    @Override
    public Collection<IGroup> loadGroupsByCriteria(int maxResult, int startResult, boolean isSort, String field) {
        return groupDAO.load(maxResult, startResult, isSort, field);
    }

    @Override
    public Collection<IRole> loadRolesByCriteria(int maxResult, int startResult, boolean isSort, String field) {
        return roleDAO.load(maxResult, startResult, isSort, field);
    }

    @Override
    public IUser removeUser(Long id) {
        IUser user = userDAO.find(id);
        if (user == null) {
            log.error("could not found user by id: {}", id);
            return null;
        }
        userDAO.delete(id);
        return user;
    }

    @Override
    public IRole removeRole(Long id) {
        IRole role = roleDAO.find(id);
        if (role == null) {
            log.error("could not found role by id: {}", id);
            return null;
        }
        roleDAO.delete(id);
        return role;
    }

    @Override
    public IGroup removeGroup(Long id) {
        IGroup group = groupDAO.find(id);
        if (group == null) {
            log.error("could not found group by id : {}", id);
            return null;
        }
        groupDAO.delete(id);
        return group;
    }

    @Override
    public void includeUserToRoles(IUser user, Collection<Long> roleIds) {
        log.info("include user: {} to role by ids: {}", user, roleIds);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            entityManager.getTransaction().begin();
            User u = (User) loadUser(user);
            if (u == null) {
                log.warn("can't found user:{}", user);
                return;
            }
            if (roleIds == null || roleIds.isEmpty()) {
                log.warn("roles can't be empty");
                return;
            }
            Collection<Role> roles = roleDAO.load(roleIds);
            roles.addAll(u.getRoles());
            u.setRoles(new HashSet<Role>(roles));
            entityManager.merge(u);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            log.error("can't include user {} to roles {}", user, roleIds);
            log.debug("", e);
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
        } finally {
            if (entityManager != null && entityManager.isOpen()) {
                entityManager.close();
            }
        }
    }

    @Override
    public void includeUserToGroups(IUser user, Collection<Long> groupIds) {
        log.info("include user {} to groups by ids:{}", user, groupIds);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            entityManager.getTransaction().begin();
            User u = (User) loadUser(user);
            if (u == null) {
                log.warn("can't found user:{}", user);
                return;
            }
            if (groupIds == null || groupIds.isEmpty()) {
                log.warn("groups  can't be empty");
                return;
            }
            Collection<Group> groups = groupDAO.load(groupIds);//PersistenceUtils.loadEntitiesByIds(em, Group.class, groupIds);
            groups.addAll(u.getGroups());
            u.setGroups(new HashSet<Group>(groups));
            entityManager.merge(u);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            log.error("can't include user {} to groups {}", user, groupIds);
            log.debug("", e);
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
        } finally {
            if (entityManager != null && entityManager.isOpen()) {
                entityManager.close();
            }
        }
    }

    @Override
    public void excludeUserFromRoles(IUser user, Collection<Long> roles) {
        log.info("exclude user {} from roles:{}", user, roles);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        Set<Role> uRoles = new HashSet<Role>();
        try {
            entityManager.getTransaction().begin();
            User u = (User) loadUser(user);
            if (u == null) {
                log.warn("can't found user:{}", user);
                return;
            }
            if (roles == null || roles.isEmpty()) {
                log.warn("roles can't be empty");
                return;
            }
            Set<Long> newRoles = new HashSet<Long>();
            for (Role r : u.getRoles()) {
                newRoles.add(r.getId());
            }
            newRoles.removeAll(roles);

            if (!newRoles.isEmpty()) {
                Collection<Role> rs = roleDAO.load(newRoles);//PersistenceUtils.loadEntitiesByIds(em, Role.class, newRoles);
                uRoles.addAll(rs);
            }
            u.setRoles(uRoles);
            entityManager.merge(u);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            log.error("can't exclude user {} from roles {}", user, uRoles);
            log.debug("", e);
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
        } finally {
            if (entityManager != null && entityManager.isOpen()) {
                entityManager.close();
            }
        }
    }

    @Override
    public void excludeUserFromGroups(IUser user, Collection<Long> groups) {
        log.info("exclude user {} to groups by ids:{}", user, groups);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        Set<Group> uGroup = new HashSet<Group>();
        try {
            entityManager.getTransaction().begin();
            User u = (User) loadUser(user);
            if (u == null) {
                log.warn("can't found user:{}", user);
                return;
            }
            if (groups == null || groups.isEmpty()) {
                log.warn("groups  can't be empty");
                return;
            }
            Set<Long> newGroups = new HashSet<Long>();
            for (Group g : u.getGroups()) {
                newGroups.add(g.getId());
            }
            newGroups.removeAll(groups);

            if (!newGroups.isEmpty()) {
                Collection<Group> gs = groupDAO.load(newGroups);//PersistenceUtils.loadEntitiesByIds(em, Group.class, newGroups);
                uGroup.addAll(gs);
            }
            u.setGroups(uGroup);
            entityManager.merge(u);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            log.error("can't exclude user {} from groups {}", user, uGroup);
            log.debug("", e);
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
        } finally {
            if (entityManager != null && entityManager.isOpen()) {
                entityManager.close();
            }
        }
    }

    @Override
    public IUser saveOrUpdate(IUser user) {
        return userDAO.update((User) user);
    }

    @Override
    public IRole saveOrUpdate(IRole role) {
        if (role == null) {
            log.warn("role should be not null");
            return null;
        }
        return roleDAO.update((Role) role);
    }

    @Override
    public IGroup saveOrUpdate(IGroup group) {
        if (group == null) {
            log.warn("group should be not null");
            return null;
        }
        return groupDAO.update((Group) group);
    }

    @Override
    public Collection<IGroup> findAllowedGroups(Long permission) {
        return groupDAO.findAllowed(permission);
    }

    @Override
    public Collection<IRole> findAllowedRoles(Long permission) {
        return roleDAO.findAllowed(permission);
    }

    @Override
    public boolean isAllowedUserAccess(IUser user, IGroup group) {
        if (group == null || group.getPermission() == null) {
            log.warn("group can't be null");
            return false;
        }
        IUser u = loadUser(user);
        if (u == null) {
            log.warn("can't found user: {}", user);
            return false;
        }

        for (IGroup gr : findUserGroupsByUserId((Long) u.getId())) {
            if (gr.getPermission() <= group.getPermission()) {
                log.debug("user :{} has allowed access to group:{}", user, group);
                return true;
            }
        }
        log.debug("user :{} hasn't allowed access to group:{}", user, group);
        return false;
    }

    @Override
    public boolean isAllowedUserAccess(IUser user, IRole role) {
        if (role == null || role.getPermission() == null) {
            log.warn("role can't be null");
            return false;
        }
        IUser u = loadUser(user);
        if (u == null) {
            log.warn("can't found user: {}", user);
            return false;
        }
        for (IRole r : findUserRolesByUserId((Long) u.getId())) {
            if (r.getPermission() <= role.getPermission()) {
                log.debug("user :{} has allowed access to role:{}", user, role);
                return true;
            }
        }
        log.debug("user :{} hasn't allowed access to role:{}", user, role);
        return false;
    }

    @Override
    public IUser findUserByName(String name) {
        return userDAO.findByName(name);
    }

    @Override
    public IUser findUserByNickName(String nickName) {
        return userDAO.findByNickName(nickName);
    }

    @Override
    public IRole findRoleByName(String name) {
        return roleDAO.findByName(name);
    }

    @Override
    public IGroup findGroupByName(String name) {
        return groupDAO.findByName(name);
    }

    @Override
    public int usersCount() {
        return userDAO.count();
    }

    @Override
    public int rolesCount() {
        return roleDAO.count();
    }

    @Override
    public int groupCount() {
        return groupDAO.count();
    }

    @Override
    public IUser newUserInstance() {
        return userDAO.newInstance();
    }

    @Override
    public IRole newRoleInstance() {
        return roleDAO.newInstance();
    }

    @Override
    public IGroup newGroupInstance() {
        return groupDAO.newInstance();
    }

    @Override
    public void enableUserToRoles(IUser user, Collection<IRole> roles) {
        Set<Long> roleIds = new HashSet<Long>();
        for (IRole r : roles) {
            roleIds.add((Long) r.getId());
        }
        includeUserToRoles(user, roleIds);
    }

    @Override
    public void disableUserFromRoles(IUser user, Collection<IRole> roles) {
        Set<Long> roleIds = new HashSet<Long>();
        for (IRole r : roles) {
            roleIds.add((Long) r.getId());
        }
        excludeUserFromRoles(user, roleIds);
    }

    @Override
    public void enableUserToGroups(IUser user, Collection<IGroup> groups) {
        Set<Long> groupIds = new HashSet<Long>();
        for (IGroup g : groups)
            groupIds.add((Long) g.getId());
        includeUserToGroups(user, groupIds);
    }

    @Override
    public void disableUserFromGroups(IUser user, Collection<IGroup> groups) {
        Set<Long> groupIds = new HashSet<Long>();
        for (IGroup g : groups)
            groupIds.add((Long) g.getId());
        excludeUserFromGroups(user, groupIds);
    }

    @Override
    public void reassignUserRoles(IUser user, Collection<IRole> roles) {
        Set<Long> roleIds = new HashSet<Long>();
        for (IRole<Long> r : roles)
            roleIds.add(r.getId());
        reassignUserRoleById(user, roleIds);
    }

    @Override
    public void reassignUserGroups(IUser user, Collection<IGroup> groups) {
        Set<Long> groupIds = new HashSet<Long>();
        for (IGroup<Long> g : groups)
            groupIds.add(g.getId());
        reassignUserGroupById(user, groupIds);
    }

    @Override
    public void reassignUserRoleById(IUser user, Collection<Long> roleIds) {
        log.info("reassign user {} role by id {}", user, roleIds);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            entityManager.getTransaction().begin();

            User u = (User) loadUser(user);
            if (u == null) {
                log.warn("can't find user {}", user);
                return;
            }
            if (roleIds == null || roleIds.isEmpty()) {
                log.debug("remove all roles for user:{}", user);
                u.getRoles().clear();
                entityManager.merge(u);
                entityManager.getTransaction().commit();
                return;
            }

            Collection<Role> uRoles = roleDAO.load(roleIds);//new HashSet<Role>(PersistenceUtils.loadEntitiesByIds(em, Role.class, roleIds));
            u.setRoles(new HashSet<Role>(uRoles));
            entityManager.merge(u);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            log.error("can't reassign user {} by roles id {}", user, roleIds);
            log.debug("", e);
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
        } finally {
            if (entityManager != null && entityManager.isOpen()) {
                entityManager.close();
            }
        }
    }

    @Override
    public void reassignUserGroupById(IUser user, Collection<Long> groupIds) {
        log.info("reassign user {} group by id {}", user, groupIds);
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        try {
            entityManager.getTransaction().begin();
            User u = (User) loadUser(user);
            if (u == null) {
                log.warn("can't find user {}", user);
                return;
            }
            if (groupIds == null || groupIds.isEmpty()) {
                log.debug("remove all groups for user:{}", user);
                u.getGroups().clear();
                entityManager.merge(u);
                entityManager.getTransaction().commit();
                return;
            }

            Set<Group> uGroups = new HashSet<Group>(groupDAO.load(groupIds)/*PersistenceUtils.loadEntitiesByIds(em, Group.class, groupIds)*/);
            u.setGroups(uGroups);
            entityManager.merge(u);
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            log.error("can't reassign user {} by groups id{}", user, groupIds);
            log.debug("", e);
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
        } finally {
            if (entityManager != null && entityManager.isOpen())
                entityManager.close();
        }
    }

    @Override
    public Long findUserMaxPermissionByRole(IUser user) {
        Number result = Long.MAX_VALUE;
        try {
            result = getEntityManager().createNamedQuery("user.findRoleWithMaxPermission", Number.class).setParameter("userId", user.getId()).getSingleResult();
            log.info("max permission: {} for user: {}", result.longValue(), user);
        } catch (NoResultException e) {
            log.error("findUserMaxPermissionByRole: not result found for user:", user);
            log.debug("trace: ", e);

        } catch (NonUniqueResultException e) {
            log.error("findUserMaxPermissionByRole: not unique result for user:", user);
            log.debug("trace: ", e);
        }
        return result.longValue();
    }

    @Override
    public boolean hasUserAccessToRolesId(IUser user, Collection<Long> roles) {
        Collection<Role> rs = roleDAO.load(roles);//PersistenceUtils.loadEntitiesByIds(em, Role.class, roles);
        Long maxPermission = findUserMaxPermissionByRole(user);
        for (Role role : rs) {
            if (role.getPermission() >= maxPermission) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasUserAccessToRolesByName(IUser user, Collection<String> rolesName) {
        Collection<Role> rs = PersistenceUtils.loadEntitiesByName(getEntityManager(), Role.class, rolesName);
        Long maxPermission = findUserMaxPermissionByRole(user);
        for (Role role : rs) {
            if (role.getPermission() >= maxPermission) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasUserAccessToRoles(IUser user, Collection<IRole> roles) {
        Set<Long> ids = new HashSet<Long>();
        for (IRole role : roles) {
            ids.add((Long) role.getId());
        }
        return hasUserAccessToRolesId(user, ids);
    }

    @Override
    public boolean hasUserAccessToGroupByName(IUser user, Collection<String> groupsName) {
        for (IGroup group : findUserGroupsByUserId((Long) user.getId())) {
            if (groupsName.contains(group.getName())) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean hasUserAccessToGroups(IUser user, Collection<IGroup> groups) {
        for (IGroup group : findUserGroupsByUserId((Long) user.getId())) {
            if (groups.contains(group)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public IRole newRoleInstance(String roleName, Long permission) {
        return roleDAO.newInstance(roleName, permission);
    }

    @Override
    public IGroup newGroupInstance(String groupName, Long permission) {
        return groupDAO.newInstance(groupName, permission);
    }

    @Override
    public void setUserName(IUser user, String newUserName) {
        userDAO.setUserName(user, newUserName);
    }

    @Override
    public void setPassword(IUser user, String password) {
        userDAO.setPassword(user, password);
    }

    @Override
    public void setHint(IUser user, String hint) {
        userDAO.setHint(user, hint);
    }

    @Override
    public void setNickName(IUser user, String nickName) {
        userDAO.setNickName(user, nickName);
    }

    @Override
    public IUser save(IUser iUser) {
        return userDAO.save((User) iUser);
    }

    @Override
    public void delete(Long id) {
        throw new UnsupportedOperationException("this operation not provided for ContextDAO");
    }

    @Override
    public IUser find(Long id) {
        throw new UnsupportedOperationException("this operation not provided for ContextDAO");
    }

    @Override
    public IUser update(IUser iUser) {
        throw new UnsupportedOperationException("this operation not provided for ContextDAO");
    }

}
